// FLTK implementation of a dynamic_text widget
// Copyright 2005 (C) Ralph Thomas

#include <view/dynamic_text.h>
#include <fltk/Widget.h>
#include <fltk/Input.h>
#include <fltk/Group.h>
#include <fltk/draw.h>
#include <fltk/Style.h>
#include <fltk/Color.h>
#include <fltk/Symbol.h>
#include <fltk/events.h>
#include <binder.hpp>

#include <iostream>

namespace view {
	const std::string	ellipse( "..." );

	class rename_input : public fltk::Input {
		adobe::binder_helper_t*	_binder;
		int			_minWidth;
		static void my_callback( fltk::Input* input, void* ) {
			rename_input* i( (rename_input*) input );
			input->throw_focus();
			//
			// Hide the widget.
			//
			input->clear_visible();
			if( input->parent() ) input->parent()->redraw();
			//
			// Set the binder back.
			//
			if( !i ) return;
			if( !i->_binder ) return;
			i->_binder->set( adobe::value_t( std::string( input->value() ) ) );
		}
	  public:
		rename_input() : fltk::Input( 0, 0, 10, 10 ), _binder( 0 ) {
			callback( (fltk::Callback*)&rename_input::my_callback );
			when( fltk::WHEN_ENTER_KEY_ALWAYS );
		}
		void set_binder( adobe::binder_helper_t* binder )	{ _binder = binder; }
		void set_min_width( int min_width )			{ _minWidth = min_width; }
		virtual int handle( int code ) {
			if( code == fltk::UNFOCUS ) my_callback( this, 0 );
			if( code == fltk::PUSH && (fltk::event_x() > w() || fltk::event_y() > h()) )
				my_callback( this, 0 );

			return fltk::Input::handle( code );
		}
	};
	
	static rename_input* shared_input = 0;
	class dynamic_text : public fltk::Widget {
		adobe::binder_helper_t	_binder;
		long			_characters;	///< No. characters to measure for.
		std::string		_title;
		int			_textWidth;
		//
		/// Set the title of this widget to the string contained
		/// in val.
		///
		/// \param	val	the new title string.
		//
		void _update( const adobe::value_t& val ) {
			try {
				_title = val.get<std::string>();
				fltk::setfont( labelfont(), labelsize() );
				_textWidth = 0;
				std::string title( _ellipsize( _title, w() ) );
				copy_label( title.c_str() );
			} catch( ... ) {
				copy_label( "" );
			}
			redraw();
		}
		//
		// std::string ellipsize( const std::string& source, long w ) const
		// 
		/// Attempt to ellipsize the given string such that it fits into the
		/// given width. This function assumes that the correct font has been
		/// set into the FLTK state.
		///
		/// \param	source	the string to ellipsize.
		/// \param	w	the width (pixels) to fit the string into.
		/// \return	the ellipsized string.
		//
		std::string _ellipsize( const std::string& source, int w ) {
			//
			// This static is *only* ok if all of the fonts are the same.
			//
			static int ellipsisWidth = 0; int h;
			if( !ellipsisWidth )
				fltk::measure( ellipse.c_str(), ellipsisWidth, h, flags() );
			if( !_textWidth )
				fltk::measure( source.c_str(), _textWidth, h, flags() );
			//
			// Perhaps this string will fit?
			//
			if( _textWidth <= w ) return source;
			if( ellipsisWidth >= w ) return ellipse;
			//
			// XXX: This is inefficient! Some kind of binary search
			// XXX: would be better.
			//
			int tmpWidth( _textWidth );
			std::string out( source );
			while( tmpWidth + ellipsisWidth > w ) {
				out.erase( --out.end() );
				fltk::measure( out.c_str(), tmpWidth, h, flags() );
			}
			out += ellipse;
			return out;
		}
	  public:
		dynamic_text( const adobe::factory_token_t& token,
			const adobe::dictionary_t& params, float target_size )
		: fltk::Widget( 0, 0, 10, 10 ), _binder( token.sheet_m ), _characters( 5 ),
	          _textWidth( 0 ) {
			box( fltk::NO_BOX );
			textcolor( fltk::BLACK );
			textsize( target_size );
			labelsize( target_size );
			flags( fltk::ALIGN_CLIP | fltk::ALIGN_INSIDE_LEFT | fltk::RAW_LABEL );
			//
			// Take an initial label for best_bounds (in case the binder
			// is empty).
			//
			copy_label( " " );
			//
			// We need to bind against something to get our text.
			//
			adobe::value_t bind =
				params.at( adobe::static_name_t( "bind" ) );
			_binder.initialize( bind,
				boost::bind( &dynamic_text::_update, this, _1 ),
				boost::bind( &dynamic_text::activate, this, _1 ) );
			//
			// We measure against a certain number of characters, rather
			// than some actual text.
			//
			params.get<long>( adobe::static_name_t( "characters" ),
				_characters );
		}
		virtual int handle( int code ) {
			if( code == fltk::PUSH ) return 1;
			if( code != fltk::RELEASE ) return fltk::Widget::handle( code );
			if( fltk::event_x() > w() || fltk::event_y() > h() ) return 1;
			if( !shared_input ) {
				shared_input = new rename_input();
				shared_input->textsize( textsize() );
				shared_input->labelsize( labelsize() );
				shared_input->box( fltk::THIN_DOWN_BOX );
			} else if( shared_input->parent() ) {
				shared_input->set_visible();
				shared_input->parent()->redraw();
			}
			int boxw, boxh;
			fltk::Rectangle r( 0, 0, 100, 100 );
			shared_input->box()->inset( r );
			boxw = 100 - r.w(); boxh = 100 - r.h();
			fltk::setfont( labelfont(), labelsize() );
			boxw += (long)(fltk::getwidth(":") + 2.5f);
			shared_input->x( x() ); shared_input->y( y() );
			shared_input->w( _textWidth + boxw ); shared_input->h( h() + boxh );
			shared_input->value( _title.c_str(), _title.size() );
			shared_input->set_binder( &_binder );
			//
			// Odd hack to see if we can locate the last period in the filename
			// and thus isolate the extension. If not then we just select the
			// entire name.
			//
			// XXX: It should be possible to choose what the delimiters are.
			//
			std::string::size_type ext( _title.rfind( "." ) );
			if( ext == std::string::npos ) ext = _title.size();
			shared_input->position( 0, ext );
			if( parent() ) {
				parent()->add( shared_input );
				parent()->redraw();
			}
			shared_input->take_focus();
			return 1;
		}
		virtual void draw() {
			//
			// This method determines if we should have black or white text
			// based on the parent's background.
			//
			if( parent() ) {
				textcolor( parent()->textcolor() );
				labelcolor( parent()->labelcolor() );
			}
			fltk::Widget::draw();
		}
		adobe::extents_t best_bounds() {
			adobe::extents_t result;
			int w = 0; int h = 0;
			//
			// This code is based on that in ui_core_implementation for
			// FLTK.
			//
			std::string text( "m", _characters );
			fltk::setfont( labelfont(), labelsize() );
			fltk::measure( text.c_str(), w, h, flags() );
			result.width() = w;
			result.height() = (long)(h + leading());
			return result;
		}
		long best_height_given_width( long width ) {
			fltk::setfont( labelfont(), labelsize() );
			//
			// Use fltk::measure to determine the best width.
			//
			int w = width; int h = 0;
			fltk::measure( label(), w, h, fltk::ALIGN_WRAP );
			return (long)(h + leading());
		}
		void place( const adobe::point_2d_t& pos, const adobe::extents_t& geo ) {
			resize( pos.x_m, pos.y_m, geo.width(), geo.height() );
			fltk::setfont( labelfont(), labelsize() );
			std::string title = _ellipsize( _title, w() );
			copy_label( title.c_str() );
		}
	};
	
	adobe::widget_node_t dynamic_text_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token ) {
		//
		// Figure out what size the text should be.
		//
		float target_size = 10.0f;
		if( parent.size_m == size_mini_s )		target_size = 6.0f;
		else if( parent.size_m == size_small_s )	target_size = 8.0f;
		else if( parent.size_m == size_normal_s )	target_size = 10.0f;
		//
		// Make a new dynamic_text widget and set it to delete with the
		// assemblage.
		//
		dynamic_text* dt( new dynamic_text( token, parameters, target_size ) );
		token.assemblage_m.delete_on_destruction( dt );
		//
		// Insert this widget into it's parent on the main display.
		//
		fltk::Widget* w( dt );
		adobe::display_t::position_t display_token( adobe::insert(
			adobe::get_main_display(), parent.display_token_m,
			w ) );
		adobe::display_t::position_t overlay_token(
			token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// Bind the calculate and place signals for Eve.
		// XXX: also bind calculate vertical.
		//
		adobe::eve_t::signal_suite_t signals;
		signals.eve_calculate_proc_m = boost::bind(
			&eve_client::utility_widget_calculate,
			dt->best_bounds(), _1 );
		eve_client::place_func_t placer(
			boost::bind( &dynamic_text::place, dt, _1, _2 ) );
		signals.eve_place_proc_m = boost::bind(
			&eve_client::utility_widget_place,
			placer, _1, _2,
			boost::ref( token.overlay_m ), overlay_token );
		//
		// Register the widget with the layout engine.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					adobe::eve_t::calculate_data_t(),
					false,
					parameters,
					signals )
				)
			);
		//
		// Return the new dynamic_text.
		//
		return adobe::widget_node_t( parent.size_m, eve_token, display_token,
			overlay_token );
	}
};

